package org.lisy.java.io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;

import com.alibaba.fastjson.util.IOUtils;

/**
 * gzip解压和生成操作
 * 
 * @author lisy
 */
public class GZipOperate {
	
	// 原始压缩文件目录
	private final static String RIGINAL_GZIP_FILE_DIR = "./deploy/riginal";
	// 原始压缩文件
	private final static String RIGINAL_GZIP_FILE = "riginal.tar.gz";
	// 目标压缩文件目录
	private final static String TARGET_GZIP_FILE_DIR = "./deploy/target/compressed";
	// 目标压缩文件
	private final static String TARGET_GZIP_FILE = "target.tar.gz";

	public static void main(String[] args) {
		String tempPath = RIGINAL_GZIP_FILE_DIR + File.separator + RIGINAL_GZIP_FILE.replace(".tar.gz", "_") + System.currentTimeMillis();
		try {
			// 解压文件
			ungzipFile(tempPath);
			
			// 压缩文件
			gzipFile(tempPath);
		} finally {
			// 删除临时文件
			delFiles(tempPath);
		}
	}

	private static void ungzipFile(String tempPath) {
		InputStream is = null;
		BufferedInputStream bis = null;
		GzipCompressorInputStream gzIn = null;
		TarArchiveInputStream tarEntry = null;
		String path = RIGINAL_GZIP_FILE_DIR + File.separator + RIGINAL_GZIP_FILE;
		try {
			is = Files.newInputStream(Paths.get(path));
			bis = new BufferedInputStream(is);
			gzIn = new GzipCompressorInputStream(bis);
			tarEntry = new TarArchiveInputStream(gzIn);
			ArchiveEntry entry = null;
		    while ((entry = tarEntry.getNextEntry()) != null) {
		    	// 获取解压文件目录，并判断文件是否损坏
		        Path newPath = zipSlipProtect(entry, Paths.get(tempPath));
		        if (entry.isDirectory()) {
		        	//创建解压文件目录
		        	Files.createDirectories(newPath);
		        } else {
		        	//再次校验解压文件目录是否存在
		        	Path parent = newPath.getParent();
		        	if (parent != null) {
		        		if (Files.notExists(parent)) {
		        			Files.createDirectories(parent);
		        		}
		        	}
		        	// 将解压文件输入到TarArchiveInputStream，输出到磁盘newPath目录
		        	Files.copy(tarEntry, newPath, StandardCopyOption.REPLACE_EXISTING);
		        }
		    }
		    tarEntry.close();
		    gzIn.close();
		    bis.close();
			is.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(e.toString());
			throw new RuntimeException(e);
		} finally {
			IOUtils.close(tarEntry);
			IOUtils.close(gzIn);
			IOUtils.close(bis);
			IOUtils.close(is);
		}
	}
	
	/**
	 * 判断压缩文件是否被损坏，并返回该文件的解压目录
	 * 
	 * @param entry
	 * @param targetDir
	 * @return
	 * @throws IOException
	 */
	private static Path zipSlipProtect(ArchiveEntry entry, Path targetDir) throws IOException {
		Path targetDirResolved = targetDir.resolve(entry.getName());
		Path normalizePath = targetDirResolved.normalize();
		if (!normalizePath.startsWith(targetDir.normalize())) {
		  throw new IOException("compressor file damaged: " + entry.getName());
		}
		return normalizePath;
	}

	private static void gzipFile(String tempPath) {
		List<String> pathList = getAllFile(tempPath, false);
		OutputStream fOut = null;
		BufferedOutputStream buffOut = null;
		GzipCompressorOutputStream gzOut = null;
		TarArchiveOutputStream tOut = null;
		try {
			
			fOut = Files.newOutputStream(Paths.get(TARGET_GZIP_FILE_DIR + File.separator + TARGET_GZIP_FILE));
			buffOut = new BufferedOutputStream(fOut);
			gzOut = new GzipCompressorOutputStream(buffOut);
			tOut = new TarArchiveOutputStream(gzOut, "UTF-8");
			// 取消解压文件大小限制
			tOut.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
			for (String path : pathList) {
				 //将该文件放入tar包，并执行gzip压缩
		        TarArchiveEntry tarEntry = new TarArchiveEntry(Paths.get(path).toFile().getName());
		        tarEntry.setSize(Paths.get(path).toFile().length());
		        tOut.putArchiveEntry(tarEntry);
		        Files.copy(Paths.get(path), tOut);
		        tOut.closeArchiveEntry();
			}
			tOut.finish();
			gzOut.close();
			fOut.close();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println(e.toString());
			throw new RuntimeException(e);
		} finally {
			IOUtils.close(tOut);
			IOUtils.close(gzOut);
			IOUtils.close(fOut);
		}
	}
	
	/**
	 * 删除目录及目录下的所有子目录和文件
	 * 
	 * @param path 文件或目录路径
	 * @return
	 */
	public static boolean delFiles(String path) {
		boolean result = false;
		File file = new File(path);
		// 判断目录
		if (file.isDirectory()) {
			File[] childrenFiles = file.listFiles();
			for (File childFile : childrenFiles) {
				result = delFiles(childFile.getPath());
				if (!result) {
					return result;
				}
			}
		}
		// 删除 文件、空目录
		return file.delete();
	}
	
    /**
     * 获取路径下的所有文件/文件夹
     * 
     * @param directoryPath 需要遍历的文件夹路径
     * @param isAddDirectory 是否将子文件夹的路径也添加到list集合中
     * @return
     */
    public static List<String> getAllFile(String directoryPath, boolean isAddDirectory) {
        List<String> list = new ArrayList<String>();
        File baseFile = new File(directoryPath);
        if (baseFile.isFile() || !baseFile.exists()) {
            return list;
        }
        File[] files = baseFile.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                if(isAddDirectory){
                    list.add(file.getAbsolutePath());
                }
                list.addAll(getAllFile(file.getAbsolutePath(), isAddDirectory));
            } else {
                list.add(file.getAbsolutePath());
            }
        }
        return list;
    }
}
